package com.bym.mall.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bym.mall.entity.Product;
import com.bym.mall.enums.ProductStatusEnum;
import com.bym.mall.enums.ResponseEnum;
import com.bym.mall.mapper.ProductMapper;
import com.bym.mall.service.ICategoryService;
import com.bym.mall.service.IProductService;
import com.bym.mall.utils.UserUtil;
import com.bym.mall.vo.HotProductDetailVo;
import com.bym.mall.vo.ProductDetailVo;
import com.bym.mall.vo.ProductVo;
import com.bym.mall.vo.ResponseVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author bym
 * @since 2022-03-26
 */
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {

    @Autowired
    private ICategoryService categoryService;

    @Resource
    private ProductMapper productMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public ResponseVo list(Integer categoryId, Integer pageNum, Integer pageSize) {
        if (ObjectUtil.isNull(categoryId)) {
            categoryId = 0;
        }

        Set<Integer> categoryIds = categoryService.findSubCategoryIds(categoryId);
        categoryIds.add(categoryId);
        QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("status", 1);
        queryWrapper.in("category_id", categoryIds);
        Page<Product> page = new Page<>(pageNum, pageSize);
        productMapper.selectPage(page, queryWrapper);

        List<Product> products = page.getRecords();
        List<ProductVo> productVos = new ArrayList<>();
        for (Product product : products) {
            ProductVo productVo = new ProductVo();
            BeanUtils.copyProperties(product, productVo);
            productVos.add(productVo);
        }
        Page<ProductVo> newPage = new Page<>();
        BeanUtils.copyProperties(page, newPage);
        newPage.setRecords(productVos);
        return ResponseVo.success(newPage);
    }

    @Override
    public ResponseVo detail(Integer productId) {
        //从redis中查找商品信息
        Map<Object, Object> cacheBeanMap = redisTemplate.opsForHash().entries("ProductService:detail:" + productId);
        if (CollectionUtil.isNotEmpty(cacheBeanMap)) {
            ProductDetailVo cacheBean = BeanUtil.toBean(cacheBeanMap, ProductDetailVo.class);
            //记录商品访问量
            addVisitScore(cacheBean.getCategoryId(), cacheBean.getId());
            return ResponseVo.success(cacheBean);
        }

        Product product = productMapper.selectById(productId);
        if (ObjectUtil.isNull(product)) {
            return ResponseVo.error(ResponseEnum.PRODUCT_NOT_EXIST);
        }

        //只对确定性条件做判断
        if (ProductStatusEnum.OFF_SALE.getCode().equals(product.getStatus()) || ProductStatusEnum.DELETE.getCode().equals(product.getStatus())) {
            return ResponseVo.error(ResponseEnum.PRODUCT_OFF_SALE_OR_DELETE);
        }

        //对库存字段做脱敏处理
        product.setStock(product.getStock() > 100 ? 100 : product.getStock());
        ProductDetailVo productDetailVo = new ProductDetailVo();
        BeanUtils.copyProperties(product, productDetailVo);

        //将商品信息存入redis，使用Hash类型，并设置5min的过期时间
        Map<String, String> beanMap = Convert.toMap(String.class, String.class, productDetailVo);
        String key = "ProductService:detail:" + productDetailVo.getId();
        redisTemplate.opsForHash().putAll(key, beanMap);
        redisTemplate.expire(key, 5, TimeUnit.MINUTES);
        //记录商品访问量
        addVisitScore(productDetailVo.getCategoryId(), productDetailVo.getId());

        return ResponseVo.success(productDetailVo);
    }

    private void addVisitScore(Integer categoryId, Integer productId) {
        //判断用户今天是否访问过该商品
        Integer uid = UserUtil.getUser().getId();
        String visitedKey = "ProductService:addVisitScore:visited:" + productId;
        Boolean isMember = redisTemplate.opsForSet().isMember(visitedKey, String.valueOf(uid));

        //用户今天已经访问该商品
        if (BooleanUtil.isTrue(isMember)) {
            return;
        }

        //用户今天没有访问该商品，增加商品的score
        redisTemplate.opsForSet().add(visitedKey, String.valueOf(uid));
        String scoreKey = "ProductService:addVisitScore:visitScore";
        redisTemplate.opsForZSet().incrementScore(scoreKey, categoryId + ":" + productId, 1);
    }

    /**
     * 添加商品
     *
     * @param product 商品
     * @return {@link ResponseVo}
     */
    @Override
    public ResponseVo add(Product product) {
        Integer uid = UserUtil.getUser().getId();
        product.setOwnerId(uid);
        //默认为在售状态
        product.setStatus(1);

        int row = productMapper.insert(product);
        if (row <= 0) {
            ResponseVo.error(ResponseEnum.ERROR);
        }

        return ResponseVo.success();
    }

    /**
     * 更新产品
     *
     * @param product 产品
     * @return {@link ResponseVo}
     */
    @Override
    public ResponseVo update(Product product) {
        int row = productMapper.updateById(product);
        if (row <= 0) {
            ResponseVo.error(ResponseEnum.ERROR);
        }

        return ResponseVo.success();
    }

    /**
     * 获取自己的商品列表
     *
     * @param status
     * @param pageNum
     * @param pageSize 页面大小
     * @return {@link ResponseVo}
     */
    @Override
    public ResponseVo myList(Integer status, Integer pageNum, Integer pageSize) {
        QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
        if (ObjectUtil.isNotNull(status)) {
            queryWrapper.eq("status", status);
        }
        queryWrapper.eq("owner_id", UserUtil.getUser().getId());
        Page<Product> page = new Page<>(pageNum, pageSize);

        productMapper.selectPage(page, queryWrapper);

        List<Product> products = page.getRecords();
        List<ProductVo> productVos = new ArrayList<>();
        for (Product product : products) {
            ProductVo productVo = new ProductVo();
            BeanUtils.copyProperties(product, productVo);
            productVos.add(productVo);
        }
        Page<ProductVo> newPage = new Page<>();
        BeanUtils.copyProperties(page, newPage);
        newPage.setRecords(productVos);

        return ResponseVo.success(newPage);
    }

    /**
     * 获取热门商品
     *
     * @param size       大小
     * @param categoryId 类别id
     * @return {@link ResponseVo}
     */
    @Override
    public ResponseVo hotProduct(Integer categoryId, Integer size) {
        //获取该类型的子类型
        Set<Integer> subCategoryIds = categoryService.findSubCategoryIds(categoryId);
        if (CollectionUtil.isEmpty(subCategoryIds)) {
            return ResponseVo.success();
        }

        //作为结果返回
        LinkedList<HotProductDetailVo> hotProducts = new LinkedList<>();

        String key = "ProductService:addVisitScore:visitScore";
        Cursor<ZSetOperations.TypedTuple<String>> cursor = redisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
        while (cursor.hasNext()) {
            ZSetOperations.TypedTuple<String> tuple = cursor.next();
            String value = tuple.getValue();
            String[] strList = StrUtil.splitToArray(value, ":");
            if (subCategoryIds.contains(Integer.valueOf(strList[0]))) {
                ResponseVo responseVo = detail(Integer.valueOf(strList[1]));
                ProductDetailVo productDetailVo = (ProductDetailVo) responseVo.getData();
                HotProductDetailVo hotProductDetailVo = new HotProductDetailVo();
                BeanUtil.copyProperties(productDetailVo, hotProductDetailVo);
                hotProductDetailVo.setScore(tuple.getScore());
                hotProducts.addFirst(hotProductDetailVo);
            }
        }

        return ResponseVo.success(CollectionUtil.sub(hotProducts, 0, size));
    }
}
